'use strict'

let util = require('util');
let models = require('../models');
let errors = require('../../config/errors');

let CheckTimeStatus = models.CheckTimeStatus;
let Attendance = models.Attendance;
let AttendanceValue = models.AttendanceValue;
let AttendanceStatus = models.AttendanceStatus;
let AttendanceDescriptions = models.AttendanceDescriptions;

let AttendanceDao = models.AttendanceDao;
let AttendanceValueDao = models.AttendanceValueDao;
let RawAttendanceDao = models.RawAttendanceDao;

let userService = require('./user');

function isAttended(status) {
    return status == AttendanceStatus.Attended || status == AttendanceStatus.AdditionalAttend;
}

function getStatisticsOfNatureClass(natureClass, beginDate, endDate) {
    let statisticsList = [];

    let curDate = new Date(beginDate);

    while ( curDate < endDate ) {
        let curEndDate = new Date(curDate);
        curEndDate.setDate(curEndDate.getDate() + 1);

        let attendances = getAttendancesOfNatureClass(natureClass, curDate, curEndDate);
        let statistics = emptyStatistics();
        attendances.forEach(attendance => {
            reduceAttendance(statistics, attendance);
        });
        statisticsList.push(statistics);

        curDate = curEndDate;
    }

    return statisticsList;
}

function getStatisticsOfNatureClassToday(natureClass) {
    let statistics = {
        attendCount: 0,
        lateCount: 0,
        earlyCount: 0,
        restCount: 0,
        absenceCount: 0,
        totalAbsence: 0
    };

    natureClass.students.forEach(student => {
        let attendance = getAttendanceOfStudentToday(student)
        reduceAttendance(statistics, attendance);
    });

    return statistics;
}

function emptyStatistics() {
    return {
        attendCount: 0,
        lateCount: 0,
        earlyCount: 0,
        restCount: 0,
        absenceCount: 0,
        totalAbsence: 0
    };
}

function combineStatistics(result, nextStatistics) {
    Object.keys(result).forEach(key => {
        result[key] += nextStatistics[key];
    });
}

function getStatisticsOfStudent(student, startTime, endTime) {
    let statistics = {
        attendCount: 0,
        lateCount: 0,
        earlyCount: 0,
        restCount: 0,
        absenceCount: 0,
        totalAbsence: 0
    };

    let attendances = queryAttendancesOfStudent(student, startTime, endTime, 0);
    attendances.forEach(attendance => {
        reduceAttendance(statistics, attendance);
    });

    return statistics;
}

function reduceAttendance(statistics, attendance) {
    let attendanceValues = combineAttendanceValues(attendance);

    let isAttended = true;
    if ( attendanceValues.has(AttendanceStatus.Late) ) {
        statistics.lateCount ++;
        isAttended = false;
    }
    if ( attendanceValues.has(AttendanceStatus.Early) ) {
        statistics.earlyCount ++;
        isAttended = false;
    }
    if ( attendanceValues.has(AttendanceStatus.Rest) ) {
        statistics.restCount ++;
        isAttended = false;
    }
    if ( attendanceValues.has(AttendanceStatus.Absent) ) {
        statistics.absenceCount ++;
        isAttended = false;
    }
    if ( isAttended && attendanceValues.size > 0 ) {
        statistics.attendCount ++;
    }
    else {
        statistics.totalAbsence ++;
    }
}

function combineAttendanceValues(attendance) {
    let statusSet = new Set();

    if ( attendance ) {
        attendance.values.forEach(values => {
            if ( values.enterStatus != AttendanceStatus.Created ) {
                statusSet.add(values.enterStatus);
            }
            if ( values.exitStatus != AttendanceStatus.Created ) {
                statusSet.add(values.exitStatus);
            }
        });
    }

    return statusSet;
}

function getAttendancesOfNatureClassToday(natureClass) {
    let date = new Date();
    let today = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0);
    let tomorrow = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 24, -1);

    return AttendanceDao.find({
        student: {
            $in: natureClass.students.map(student => student.id)
        },
        date: {
            $gte: today,
            $lte: tomorrow
        }
    }).sort((attendance1, attendance2) => {
        return attendance1.student.id < attendance2.student.id;
    });
}

function getAttendancesOfNatureClass(natureClass, beginDate, endDate) {
    return AttendanceDao.find({
        student: {
            $in: natureClass.students.map(student => student.id)
        },
        date: {
            $gte: beginDate,
            $lte: endDate
        }
    }).sort((attendance1, attendance2) => {
        return attendance1.student.id > attendance2.student.id;
    });
}

function getAttendanceOfStudentToday(student) {
    let date = new Date();
    let today = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, -1);
    let tomorrow = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 24, -1);

    return AttendanceDao.findOne({
        student: student.id,
        date: {
            $gte: today,
            $lte: tomorrow
        }
    });
}

function getAttendancesOfStudentThisWeek(student) {
    let firstDay = new Date();
    let lastDay = new Date();

    // 使用0 - 6分别表示周一到周日
    let today = firstDay.getDay();
    if ( today == 0 ) {
        today = 7;
    }
    today --;

    firstDay.setDate(firstDay.getDate() - today);
    firstDay.setHours(0);
    firstDay.setMinutes(-1);
    firstDay.setSeconds(0);

    lastDay.setDate(lastDay.getDate() + (7 - today));
    lastDay.setHours(0);
    lastDay.setMinutes(0);
    lastDay.setSeconds(0);

    console.log(firstDay);
    console.log(lastDay);

    return getAttendancesOfStudent(student, firstDay, lastDay);
}

function getAttendancesOfStudent(student, beginTime, endTime) {
    return AttendanceDao.find({
        student: student.id,
        date: {
            $gte: beginTime,
            $lte: endTime
        }
    });
}

function queryAttendancesOfStudent(student, startTime, endTime, queryStatus) {
    let attendances = AttendanceDao.find({
        student: student.id,
        date: {
            $gte: startTime,
            $lte: endTime
        }
    });

    if ( queryStatus == 1 ) {
        return attendances.filter(attendance => {
            let result = true;
            attendance.values.forEach(value => {
                if ( 
                    (value.enterStatus != AttendanceStatus.Attended &&
                    value.enterStatus != AttendanceStatus.Created &&
                    value.enterStatus != AttendanceStatus.AdditionalAttend) ||
                    (value.exitStatus != AttendanceStatus.Attended &&
                    value.exitStatus != AttendanceStatus.Created &&
                    value.exitStatus != AttendanceStatus.AdditionalAttend)
                    ) {
                    result = false;
                }
            });

            return result;
        });
    }

    if ( queryStatus != 0 ) {
        return attendances.filter(attendance => {
            let result = false;
            attendance.values.forEach(value => {
                if (
                    value.enterStatus == queryStatus ||
                        value.exitStatus == queryStatus
                ) {
                    result = true;
                }
            });
            console.log(result);

            return result;
        });
    }

    return attendances;
}

function queryAttendancesOfStudents(students, startTime, endTime, queryStatus) {
    let studentIds = students.map(student => student.id);

    let attendances = AttendanceDao.find({
        student: {
            $in: studentIds
        },
        date: {
            $gte: startTime,
            $lte: endTime
        }
    });

    console.log(attendances.length);

    if ( queryStatus == 1 ) {
        return attendances.filter(attendance => {
            let result = true;
            attendance.values.forEach(value => {
                if ( 
                    (value.enterStatus != AttendanceStatus.Attended &&
                    value.enterStatus != AttendanceStatus.Created &&
                    value.enterStatus != AttendanceStatus.AdditionalAttend) ||
                    (value.exitStatus != AttendanceStatus.Attended &&
                    value.exitStatus != AttendanceStatus.Created &&
                    value.exitStatus != AttendanceStatus.AdditionalAttend)
                    ) {
                    result = false;
                }
            });

            return result;
        });
    }

    if ( queryStatus != 0 ) {
        return attendances.filter(attendance => {
            let result = false;
            attendance.values.forEach(value => {
                if (
                    value.enterStatus == queryStatus ||
                        value.exitStatus == queryStatus
                ) {
                    result = true;
                }
            });

            return result;
        });
    }

    return attendances;
}

function filterStudentsByStatus(students, status) {
    return students.filter(student => {
        let statistics = student.statistics;
        if ( status == AttendanceStatus.Created ) {
            return true;
        }
        else if ( status == AttendanceStatus.Attended ) {
            return statistics.totalAbsence == 0;
        }
        else if ( status == AttendanceStatus.Rest ) {
            return statistics.restCount != 0;
        }
        else if ( status == AttendanceStatus.Absent ) {
            return statistics.absenceCount != 0;
        }
        else if ( status == AttendanceStatus.Late ) {
            return statistics.lateCount != 0;
        }
        else if ( status == AttendanceStatus.Early ) {
            return statistics.earlyCount != 0;
        }

        return false;
    });
}

let attendanceMessageTemplate = "%s %s %s";
function formatAttendanceMessages(attendance) {
    let attedanceValues = combineAttendanceValues(attendance);

    return Array.from(attedanceValues)
        .filter(value => value != AttendanceStatus.Attended && value != AttendanceStatus.AdditionalAttend)
        .map(value => AttendanceDescriptions.get(value))
        .map(description => util.format(attendanceMessageTemplate,
                    attendance.student.sid, attendance.student.name, description));
}

function updateAttendanceValues(attendanceValues) {
    attendanceValues.forEach(value => {
        AttendanceValueDao.update({
            id: value.id
        }, {
            $set: {
                enterStatus: value.enterStatus,
                exitStatus: value.exitStatus
            }
        });
    });
}

function getRawAttendancesOfDevice(startTime, endTime, options) {
    var condition = {
        time: {
            $gte: startTime,
            $lte: endTime
        }
    };

    Object.keys(options).forEach(option => {
        condition[option] = options[option];
    });

    console.log(condition);

    return RawAttendanceDao.find(condition);
}

function inWhichCheckTime(time, checkTimeSet) {
    let day = time.getDay();
    let checkTimes = null;
    if ( day == 0 ) {
        checkTimes = checkTimeSet.sunday;
    }
    else if ( day == 6 ) {
        checkTimes = checkTimeSet.saturday;
    }
    else {
        checkTimes = checkTimeSet.weekdays;
    }

    return checkTimes.findIndex(checkTime => {
        return isInCheckTime(time, checkTime);
    });
}

function isInCheckTime(time, checkTime) {
    let hour = time.getHours();
    let minute = time.getMinutes();

    return checkTime.status == CheckTimeStatus.Used &&
        toMinutes(hour, minute) >= toMinutes(checkTime.startHour, checkTime.startMinute) &&
            toMinutes(hour, minute) <= toMinutes(checkTime.endHour, checkTime.endMinute);
}

function lateTime(time, checkTimeSet) {
    return inWhichCheckTime(time, checkTimeSet);
}

function earlyTime(time, checkTimeSet) {
    return inWhichCheckTime(time, checkTimeSet);
}

function nextTime(time, checkTimeSet) {
    let day = time.getDay();
    let checkTimes = null;
    if ( day == 0 ) {
        checkTimes = checkTimeSet.sunday;
    }
    else if ( day == 6 ) {
        checkTimes = checkTimeSet.saturday;
    }
    else {
        checkTimes = checkTimeSet.weekdays;
    }

    return checkTimes.findIndex(checkTime => {
        let hour = time.getHours();
        let minute = time.getMinutes();

        return toMinutes(hour, minute) <= toMinutes(checkTime.startHour, checkTime.startMinute);
    });
}

function lastTime(time, checkTimeSet) {
    let day = time.getDay();
    let checkTimes = null;
    if ( day == 0 ) {
        checkTimes = checkTimeSet.sunday;
    }
    else if ( day == 6 ) {
        checkTimes = checkTimeSet.saturday;
    }
    else {
        checkTimes = checkTimeSet.weekdays;
    }

    return checkTimes.findIndex(checkTime => {
        let hour = time.getHours();
        let minute = time.getMinutes();

        return toMinutes(hour, minute) >= toMinutes(checkTime.startHour, checkTime.startMinute);
    });
}

// 将小时和分钟都转换为分钟
function toMinutes(hour, minute) {
    return hour * 60 + minute;
}

module.exports = {
    getAttendancesOfNatureClassToday,
    getStatisticsOfNatureClassToday,
    getStatisticsOfNatureClass,
    getAttendancesOfStudentThisWeek,
    getAttendanceOfStudentToday,
    getAttendancesOfStudent,
    getAttendancesOfNatureClass,
    queryAttendancesOfStudent,
    queryAttendancesOfStudents,
    getStatisticsOfStudent,
    emptyStatistics,
    combineStatistics,
    filterStudentsByStatus,
    formatAttendanceMessages,
    updateAttendanceValues,
    getRawAttendancesOfDevice,
    lateTime,
    earlyTime,
    nextTime,
    lastTime
};
